#pragma once

#include "log.hpp"

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>

// 网络常用的四个头文件
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/wait.h>
#include <pthread.h>

const int defaultlistenfd = -1;
const std::string defaultip = "0.0.0.0"; // 云服务器无法直接绑定公网ip
const uint16_t defaultport = 8090;
const int backlog = 10;

enum
{
    socketerr = 2,
    binderr,
    listenerr
};


//Version3版本，线程函数的参数
class TcpServer; //类的声明
class ThreadData
{
public:
    ThreadData(TcpServer* tsvr, int sockfd, const std::string& clientip, const uint16_t& clientport)
    :_tsvr(tsvr), _sockfd(sockfd), _clientip(clientip), _clientport(clientport)
    {}
public:
    TcpServer* _tsvr;
    int _sockfd;
    std::string _clientip;
    uint16_t _clientport;
};

class TcpServer
{
public:
    // 其实InitServer也可以写在构造函数里。但是尽量构造函数不做任何可能出错的内容
    // 比如说创建套接字可能会失败，bind也可能会失败等等
    // 这只是一个习惯而已
    TcpServer(const std::string ip = defaultip, const uint16_t port = defaultport)
        : _listen_sockfd(defaultlistenfd), _port(port), _ip(ip)
    {
    }
    ~TcpServer() {}

    void InitServer()
    {
        // 创建套接字
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            LOG(Fatal, "create listen_socket fail");
            exit(socketerr);
        }
        LOG(Info, "create listen_sockfd success!");

        // 绑定端口号
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_aton(_ip.c_str(), &(local.sin_addr));
        // local.sin_addr.s_addr = INADDR_ANY;  //INADDR_ANY也是全0的意思。

        int ret = bind(_listen_sockfd, (const struct sockaddr *)&local, sizeof(local));
        // 与服务器的公网IP不能直接被绑定，但127.0.0.1可以
        if (ret < 0)
        {
            LOG(Fatal, "bind sockfd fail");
            exit(binderr);
        }
        LOG(Info, "bind sockfd success!");

        // 监听
        // Tcp是面向连接的，需要等别人来连接才能继续往下运行
        ret = listen(_listen_sockfd, backlog);
        if (ret < 0)
        {
            LOG(Fatal, "listen sockfd fail");
            exit(listenerr);
        }
        LOG(Info, "listen sockfd success!");
    }

    void StartServer()
    {
        for (;;)
        {
            std::cout << "server is running..." << std::endl;
            // 建立连接
            // 1.获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                LOG(Waring, "accept fail, continue listen new client");
                continue;
            }
            LOG(Info, "accept success, get a new link");
            // Udp是无连接的，相当于用一个sockfd去接受所有传来的数据
            // Tcp是有链接的，要先对双方建立连接才能进行数据通信，对于每一个Client都需要建立一个通信
            // 所以listensockfd相当于监听是否有Client来连接，对于连接的管理，则是创建了一个sockfd进行连接。

            // 当没有客户端时，可以使用telnet工具进行测试服务器，telnet默认是tcp的。
            // telnet 127.0.0.1 8080运行 ctrl+]使用 quit退出

            // 2.根据新连接进行通信
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32]; // 15其实就够了
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            std::cout << "clientip:" << clientip << ",clientport:" << clientport << std::endl;
            // 对于通信内容，读取和发送的函数会自己帮我们在网络和主机序列进行大小端转换，只不过ip和port这两个字段需要我们特殊处理
            
            //Version1:单进程版本
            // Service(sockfd);
            // close(sockfd);

            //Version2:多进程版本
            //解决无法实现多人共同访问服务器
            // pid_t id = fork();
            // //子进程
            // if(id == 0)
            // {
            //     close(_listen_sockfd);  
            //     if(fork() > 0) exit(0);
            //     //孙子进程
            //     Service(sockfd);
            //     close(sockfd);
            //     exit(0);
            // }
            // //父进程
            // close(sockfd);
            // pid_t rid = waitpid(id, nullptr, 0);
            // (void)rid;

            //Version3:多线程版本
            //解决创建进程消耗太大（也可以用进程池）
            ThreadData* td = new ThreadData(this, sockfd, clientip, clientport);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);

            //Version4:线程池版本
            //因为我们的Service服务是死循环，如果Client不退，那么线程就会越来越多。
            //所以只能满足一定量的用户就行连接和访问服务器
            //同时，客户端发送请求建立连接后，才创建的线程，耽误时间
            //所以，可以改成提供短服务，同时提前创建一些线程，引入生产消费者模型。
            //--------------------复习线程后补充-----------------//
            //tcp第二节课后半段

        }
    }

    //Version3版本支持函数
    static void* Routine(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        //线程中没有多余的文件描述符，所以不需要关闭文件描述符。
        td->_tsvr->Service(td->_sockfd);
        delete td;
        return nullptr;
    }

    void Service(int sockfd)
    {
        // 接收什么，我们发回去什么
        while (true)
        {
            // read是面向字节流的，Tcp也是，所以可以用read像读文件一样读取网络的内容
            char buffer[4096];
            ssize_t ret = read(sockfd, buffer, sizeof(buffer));
            if (ret > 0)
            {
                buffer[ret] = 0;
                std::cout << "client say#" << buffer << std::endl;
                std::string echo_string = "tcpserver echo#";
                echo_string += buffer;

                int n = write(sockfd, echo_string.c_str(), echo_string.size());
                if(n < 0) std::cout << "write error" << std::endl;
            }
            else if (ret == 0)
            {
                std::cout << "server close sockfd:" << sockfd << std::endl;
                break;
            }
            else
            {
                std::cout << "server read error" << std::endl;
                break;
            }
        }
    }

private:
    int _listen_sockfd;
    std::string _ip;
    uint16_t _port;
};

//Client: 创建套接字、bind、connect发送连接请求
//Server：创建套接字、bind、listen监听、accept建立连接

//TCP/IP是写在操作系统里的协议程序，用户使用的write和read是拷贝函数，是把数据从内核缓冲区中写入和拿出的过程。
//至于内核缓冲区什么时候发送数据、发多少、出错了怎么办，是由TCP协议（OS）决定的。TCP是控制传输协议。
//所以我们在从内核缓冲区中读数据时，就必须定制协议来确保能够识别到是否为一个完整的报文。

//其实我们在写TCP\UDP时也规定了协议，只不过没有独立成模块，例如：规定双方时字符串等
//如果我们传的是1+1让服务器计算值并返回时，就必须确定数据是否完整、数据的顺序，格式、符号等等字段。所以形成独立的模块来保证报文的合理性。
